package Quitz::Core::AccessToken;
# $Id: AccessToken.pm 7 2007-01-10 17:39:21Z nbachiyski $

=head1 NAME

Quitz::Core::AccessToken - Authentication and Authorization Control for Quitz

=cut

use strict;
use warnings;

use base qw/ Exporter Class::Accessor /;

use Quitz::Core::Exceptions;
use Exception::Class ( 'Quitz::Core::EAuthenticationError' => { isa => 'Quitz::Core::Exception' } );
use Exception::Class::TryCatch;

=head1 SYNOPSIS

    my $core = new Quitz::Core (
        # See Quitz::Core for further details on Core construction
        -ConnectInfo => { ... },
        -Credentials => { username => 'foo', password => 'bar' }
    );
    if ($core -> access_token -> allow_on_cat(0, 'rw'))
    {
        print "User `foo' is allowed both read and right access to the root category!\n";
    }

=head1 DESCRIPTION

Module Quitz::Core::AccessToken provides "access token" for Quitz::Core,
i.e. user authentication control (by username and password) and user authorization
control (by given category id and desired access).

Generally, one who writes client to the Core would rarely if ever need to access
AccessToken's methods (see L</"METHODS">) directly. Instead, he should use the Core's
own object manipulation routines which in turn shall consult with the AccessToken.

=head1 The Security Model

Quitz's security model is utterly simplified. There is just
a plain list of users, identified by their passwords. There are no groups or roles
or any stuff like that.

Access control is performed per category. Each user is assigned initially empty
access control list, consisting of category <-> access rights pairs. Each such pair
is reffered to as "explicitly assigned" rights for category.

When a user is given read access ("r") to category, he shall be able to read
category's name and all questions and their answers in that category.

When a user is given write access ("w"), he shall be able to change category's name
and parent, or delete questions and answers or change their contents. Note, however,
that there is no "x" access in the current implementation, i.e. one should have "r"
in order to "open" a category and e.g. add questions.

When a user have no B<explicitly> assigned rights for a given category, the category
tree is traversed up to the first category the user have B<explicit> access rights for
and these rights are used. If no such category is found, the request is denied.

A simple example.
Let us have the following category tree and given user has the specified rights on
the categories.

    #        A (r)                   I
    #       /\                       |
    #      /  \                      J
    #  () B    C
    #     |     \___________
    #    / \     \       \  \
    #   D  E (r)  F (rw)  G  H (w)

The user shall be able to do the following:

=over 2

=item *

Read from A, E and F - since the user has explicit "r" on them

=item *

Read from C and G - they "inherit" their "r" from A

=item *

He would be able to both read and write to F. B<HOWEVER>, even though he has "w" access to H, he
would be unable to perform B<any> actions on it, since he has no "r" (the "r" from A is not inhertied
since we have explicitly specified no-read on H). To overcome this effect one should always specifiy
"r" with "w", i.e. consider "r" to be a subset to "w".

=item *

Any access to B shall be denied, since explicit "no access" is defined. D inherits from B so all requests
to D shall be denied as well.

=item *

E, however, has "r" enabled again, so the user shall be able to read from E.

=item *

The user has no explicit rights on the entire tree I - J, so all requests on it shall be denied.

=back

In addition, each user is assigned three more flags (NOTE: Theese need refinment) that
have nothing to do with the categories but instead control user's allowed actions upon other users:

=over 2

=item *

The Inspect Flag ("i") - controls whether the user is allowed to read other users'
names, passwords and rights.

=item *

The Create Flag ("c") - controls wether the user is allowed to create or delete
(including himself) users.

=item *

The Grant Flag ("g") - controls whether the user is allowed to change access
control lists (including his own).

=back

Be aware, though, that for sane system behaviour one should be given either both
"c" and "g" or none of them, and if one is given "cg", he should also be given "i".

=head1 CONSTANTS

Quitz::Core::AcceccToken exports by default the following constants, which are actually bit
flags for use in the allow_on_cat and allow_on_user methods (see L</"METHODS">).

=over 2

=item *

S_READ - Read Access ("r")

=item *

S_WRITE - Write Access ("w")

=item *

S_INSPECT - Inspect Access ("i")

=item *

S_CREATE - Create Access ("c")

=item *

S_GRANT - Grant Access ("g")

=back

=cut

use constant
    {
        S_READ    => 1,
        S_WRITE   => 2,
        S_INSPECT => 4,
        S_CREATE  => 8,
        S_GRANT   => 16,
    };

our @EXPORT = qw( S_READ S_WRITE S_INSPECT S_CREATE S_GRANT );

sub _generate_access_token
{
	my ($self, $params) = @_;
	if (defined $params)
	{
		my $owner = $self -> owner;
		my $dbaccess = $owner -> schema;
		my $user = $dbaccess -> resultset('User') -> search
			({ username => $params -> {username},
			  password => $params -> {password} }) -> first();
		unless (defined $user)
		{
			# Authentication failed
			throw Quitz::Core::EAuthentication( error => "Authnetication failed: invalid username or password" );
		}
		# Cache access rights. Generally, it (could) involve redundant database
		# operations (reading access rights for categories we have no interest about)
		# but it's convinient
		$self -> {username} = $user -> username;
		$self -> {generic_rights} = $user -> generic_rights;
		$self -> {acl} = {};
		foreach ($user -> acl)
		{
			$self -> {acl} -> {$_ -> category_id} = $_ -> access_mask;
		}
		*Quitz::Core::AccessToken::allow_on_user = \&Quitz::Core::AccessToken::_allowuser;
		*Quitz::Core::AccessToken::allow_on_cat = \&Quitz::Core::AccessToken::_allowcat;
	}
	else
	{
		*Quitz::Core::AccessToken::allow_on_user = \&Quitz::Core::AccessToken::_any_;
		*Quitz::Core::AccessToken::allow_on_cat = \&Quitz::Core::AccessToken::_any_;
	}
}

=head1 METHODS

=head2 new $owner, \%parameters

=over 4

The constructor. It creates new access token by given reference to the current Core ($owenr)
and \%parameters in the form of { username => "a_name_of_user", password => "secret" }. Note that in
the current implementation when empty hashref is given as \%parameters an all-permissive access token
is created, i.e. it allows all operations to everyone (see L<Quitz::Core> for details).

The constructor is used internally by the Core. It's not meant for public
consumption. One should never create his own access token, or else!

=back

=cut

sub new
{
	my ($this, $owner, $params) = @_;
	my $classref = ref $this || $this;
	my $self = bless { owner => $owner }, $classref;
	$self -> _generate_access_token($params);
	return $self;
}

sub _any_
{
	return 1;
}

=head2 allow_on_cat $category, $desired_access

=over 4

Returns true value iff the authenticated user is permitted to perform all the actions
specified in $desired_access on the $category.

Category could be given either by it's numeric id (e.g. 0, 105, 666, etc.) or as
object of type Quitz::Core::Category.

Access is specified as bit-mask consisting from any combination of the S_WRITE
(for write) and S_READ (for read) flags.
See L</"The Security Model"> above for more information on the access control.

NOTE THAT one could pass "c" or "g" (see bellow) here as well, but he should
never do that if he wants to be allowed to do something.

=back

=cut

sub _allowcat
{
	my ($self, $object, $access) = @_;

	my $owner = $self -> owner;
	my $category = (ref $object) ? $object : $owner -> resultset('Category') -> find($object);
	while (defined $category)
	{
		if (exists $self -> {acl} -> {$category -> id})
		{
			return ((($self -> {acl} -> {$category -> id} || 0) & $access) == $access);
		}
		$category = $category -> parent;
	}
	return 0;
}

=head2 allow_on_user $actions

=over 4

Returns true value iff the authenticated user is allowed to execute all the
requested $actions on any other user. Again, note that only actions are
specified, but no "other user" - currently there is no way to limit one user's
control only on certain group of users.

Essentially, the Inspect (S_INSPECT), Create (S_CREATE) and Grant (S_GRANT) flags of the current user are checked.
One should never pass S_READ or S_WRITE here.

=back

=cut

sub _allowuser
{
	my ($self, $actions) = @_;
	return (($self -> {generic_rights} & $actions) == $actions);
}

__PACKAGE__ -> mk_ro_accessors(qw( owner username generic_rights acl ));

=head1 SEE ALSO

Since this module use used almost transparently by the Core, one shuld check L<Quitz::Core> for documentation
on the Quitz system.

=cut

1;
